# this takes a file created using the following command: fromelf --text -v -s -a --output=<filename> code.axf
# The output files define the variables as found in the output as their corresponding addresses.
#
#use Regexp::Fields;
use strict;

# Create a hash of all the objects (including struct fields)
my %ObjByIndex;
my %ObjByName;
my $totalObjects = 0;
my @headerlines = ();
my %ObjectRoot = ();

unless ( -e $ARGV[0] && -e $ARGV[1] && -e $ARGV[2] && -e $ARGV[3] && (length($ARGV[4]) > 0) && (6 == @ARGV))
{
    print "\n\nUsage: Missing one or more arguments.\n";
    print "The first parameter must be the full path to the fromelf object output file\n";
    print "The second parameter must be the full path to the parameter association file\n";
    print "The third parameter must be the full path to the build revision file\n";
    print "The fourth must be the output folder\n";
    print "The fifth must be the output file base name\n";
    print "The sixth must be the prepend string (ex: \"bc_\" for backup or \"\" for main)\n\n";
    exit;
}
my $ObjectFile    = $ARGV[0];
my $ParameterFile = $ARGV[1];
my $BuildRevFile  = $ARGV[2];
my $OutputFolder  = $ARGV[3];
my $OutputMapName = $ARGV[4];
my $PrependStr    = $ARGV[5];
my $OutputMapNameGuard = uc($OutputMapName) . '_H';

# Get the revision info. 
my $MajorRev = 0;
my $MinorRev = 0;
if (open (REV_FILE, "<$BuildRevFile"))
{
    $MajorRev = <REV_FILE>;
    $MajorRev =~ s/.+?(\d+).*/\1/;
    chomp $MajorRev;
    $MinorRev = <REV_FILE>;
    $MinorRev =~ s/.+?(\d+).*/\1/;
    close REV_FILE;
    chomp $MinorRev;
}
else
{
    print "$BuildRevFile file was not found!\n";
}

sub GetArraySizes($)
{
    my $Array0Size = 0;
    my $Array1Size = 0;
    my $Index = shift;
    my $FullName = $ObjByIndex{$Index}{fwname};
    my @Components = split(/\./, $FullName);
    my $Count = @Components;

    # Get the next object
    $Index++;
    my $NextName = $ObjByIndex{$Index}{fwname};
    my @NextComponents = split(/\./, $NextName);

    # Check 2d first.
    while ($NextComponents[$Count-1] =~ /\[(\d+)\]\[(\d+)\]$/)
    {
        $Array0Size = $1+1;
        $Array1Size = $2+1;
        $Index++;
        $NextName = $ObjByIndex{$Index}{fwname};
        @NextComponents = split(/\./, $NextName);
    }

    # Check 1d.
    while ($NextComponents[$Count-1] =~ /\[(\d+)\]$/)
    {
        $Array0Size = $1+1;
        $Index++;
        $NextName = $ObjByIndex{$Index}{fwname};
        @NextComponents = split(/\./, $NextName);
    }

    return ($Array0Size, $Array1Size);
}

sub ParseType($)
{
    my $index = shift;
    my $OriginalType = $ObjByIndex{$index}{type};
    my $Type = $OriginalType;

    my ($Array0Size, $Array1Size) = GetArraySizes($index);

    # First check for 2d array
    if ($Type =~ /array\[(\d+)\] of array\[(\d+)\] of.*\s+(.+)$/)
    {
        $Type = $3;
    }
    # Check for 1d array.
    elsif ($Type =~ /array\[(\d+)\] of.*\s+(.+)$/)
    {
        $Type = $2;
    }

    # Kluge...
    if ($OriginalType =~ /pointer to/)
    {
        $Type = 'pointer to ' . $Type;
    }

    return ($Array0Size, $Array1Size, $Type);
}

sub BuildInfo($)
{
    my $index = shift;
    my $info;
    if ( $ObjByIndex{$index}{bitsize} % 8 == 0 )
    {
        $info = 'address = 0x' . sprintf( "%08X", $ObjByIndex{$index}{address} ) . ', size = ' . $ObjByIndex{$index}{bytesize} . ' bytes, type = ' . $ObjByIndex{$index}{type};
    }
    else
    {
        $info = 'address = 0x' . sprintf( "%08X", $ObjByIndex{$index}{address} ) . ', size = ' . $ObjByIndex{$index}{bitsize} . ' bits ' . $ObjByIndex{$index}{bitoffset} . ' offset , type = ' . $ObjByIndex{$index}{type};
    }

    return $info;
}

sub IsStructure($$)
{
    my $Array0Size = shift;
    my $index = shift;
    if ($Array0Size > 0)
    {
        $index++; # If it's an array, start with the xxxx[0] element.
    }
    my $CurrentName = $ObjByIndex{$index}{fwname};
    my $CurrentParts = split(/\./, $CurrentName);
    my $NextName = $ObjByIndex{$index+1}{fwname};
    my $NextParts = split(/\./, $NextName);
    return ($NextParts > $CurrentParts);
}

sub BuildSingleItem($$$$)
{
    my $ObjectIndex = shift;
    my $CurrentFullName = shift;
    my $Array0Size = shift;
    my $Array1Size = shift;
    my @CurrentNameParts = split(/\./, $CurrentFullName);
    my $CurrentPartCount = @CurrentNameParts;
    my $padding = ' ' x ( ( $CurrentPartCount - 1 ) * 4 );

    my $info = BuildInfo($ObjectIndex);
    my $ExternMap = "";

    # If this isn't within a structure, add the extern map garbage.
    if ( $CurrentPartCount == 1 )
    {
        $ExternMap = "extern_map ";
    }

    my $ArrayText = "";
    if ($Array0Size > 0)
    {
        # Strip off the array index.
        $ArrayText = "[$Array0Size]";
        if ($Array1Size > 0)
        {
            $ArrayText .= "[$Array1Size]";
        }

        # Get the object index to the last item in the array.
        while (1)
        {
            # If the next item name doesn't match we're done.
            my $NextFullName = $ObjByIndex{$ObjectIndex+1}{fwname};
            if (index ($NextFullName, $CurrentFullName . '[') < 0)
            {
                last;
            }
            $ObjectIndex++;
            $ObjByIndex{$ObjectIndex}{include} = 0;# Don't include array elements in the source file.
        }
    }

    push @headerlines, $padding . "${ExternMap}int $CurrentNameParts[-1]; // $info\n";

    return $ObjectIndex;
}

sub BuildStructure($$$$)
{
    my $StartIndex = shift;
    my $CurrentFullName = shift;
    my $Array0Size = shift;
    my $Array1Size = shift;
    my @CurrentNameParts = split(/\./, $CurrentFullName);
    my $CurrentPartCount = @CurrentNameParts;
    my $StructName = $CurrentNameParts[-1];

    $ObjByIndex{$StartIndex}{include} = 0;# So it isn't addressed in the source file.

    # Find the start and end index for this struct.
    # If this is an array of structs, the actual start is one more (so the start and end all have [0] in them.
    my $ArrayText = "";
    if ($Array0Size > 0)
    {
        $ArrayText = "[$Array0Size]";
        if ($Array1Size > 0)
        {
            $ArrayText .= "[$Array1Size]";
        }

        # If this struct is an array, the start of the definition (name[0]) is the next object.
        $StartIndex++;
        $CurrentFullName = $ObjByIndex{$StartIndex}{fwname};
        $ObjByIndex{$StartIndex}{include} = 0;# So it isn't addressed in the source file.
    }
    my $EndIndex = $StartIndex;
    # Find the end of the structure definition.
    while (1)
    {
        my $NextFullName = $ObjByIndex{$EndIndex+1}{fwname};
        # If the next object doesn't start with the current name, it's not part of this struct.
        if (index ($NextFullName, $CurrentFullName . '.') < 0)
        {
            last;
        }
        $EndIndex++;
    }

    my $padding = ' ' x ( ( $CurrentPartCount - 1 ) * 4 );
    
    my $ExternMap = "";
    if ( $CurrentPartCount == 1 )
    {
        $ExternMap = "extern_map ";
    }
    my $info = BuildInfo($StartIndex);
    push @headerlines, $padding . "${ExternMap}struct ${StructName}_t // $StructName, $info\n";
    push @headerlines, $padding . "{\n";

    for (my $Index = $StartIndex + 1; $Index < $EndIndex+1; $Index++)
    {
        my $NextFullName = $ObjByIndex{$Index}{fwname};
        my @NextNameParts = split(/\./, $NextFullName);
        my $NextPartCount = @NextNameParts;

        # Get type information.
        my ($Array0Size, $Array1Size, $Type) = ParseType($Index);

        if (IsStructure($Array0Size, $Index))
        {
            $Index = BuildStructure($Index, $NextFullName, $Array0Size, $Array1Size);
        }
        else
        {
            $Index = BuildSingleItem($Index, $NextFullName, $Array0Size, $Array1Size);
        }
    }

    push @headerlines, $padding . "} $StructName$ArrayText;\n";

    # Burn down to the end of the array...
    if ($Array0Size > 0)
    {
        $CurrentFullName =~ s/\[[\[\d\]]+$//;
        # This is needed to make sure we're still looking at array elements.
        $CurrentFullName .= '[';
        while (1)
        {
            my $NextFullName = $ObjByIndex{$EndIndex+1}{fwname};
            if (index ($NextFullName, $CurrentFullName) < 0)
            {
                last;
            }
            # If this is an array element, only test to see if it's a struct root.
            if ($NextFullName =~ /\]$/)
            {
                #if (IsStructure(0, $EndIndex+1))
                {
                    $ObjByIndex{$EndIndex+1}{include} = 0;
                }
            }
            # Otherwise, see if this is the start of an array
            else
            {
                my ($Array0Size, $Array1Size) = GetArraySizes($EndIndex+1);
                # Now see if it's the start of a struct.
                if (IsStructure($Array0Size, $EndIndex+1))
                {
                    $ObjByIndex{$EndIndex+1}{include} = 0;
                }
            }
            $EndIndex++;
        }
    }

    return $EndIndex;
}

sub BuildMapHeader()
{
    push @headerlines, "#ifndef $OutputMapNameGuard\n";
    push @headerlines, "#define $OutputMapNameGuard\n\n";
    push @headerlines, "// Build Rev $MajorRev.$MinorRev\n\n";
    push @headerlines, "extern void ${PrependStr}variables(void);\n\n";
    push @headerlines, "#ifndef TSP_MAP_DEFINE_VARIABLES\n";
    push @headerlines, "#define extern_map extern\n";
    push @headerlines, "#else\n";
    push @headerlines, "#define extern_map\n";
    push @headerlines, "#endif\n\n";
    push @headerlines, "namespace ${PrependStr}fwvar\n";
    push @headerlines, "{\n";
    push @headerlines, "extern_map char * firmware_build;\n\n";

    for (my $ObjectIndex = 0; $ObjectIndex < $totalObjects ; $ObjectIndex++)
    {
        my $CurrentFullType  = $ObjByIndex{$ObjectIndex}{type};
        my $CurrentFullName  = $ObjByIndex{$ObjectIndex}{fwname};
        my @CurrentNameParts = split(/\./, $CurrentFullName);
        my $CurrentPartCount = @CurrentNameParts;

        # Get type information.
        my ($Array0Size, $Array1Size, $Type) = ParseType($ObjectIndex);

        if (IsStructure($Array0Size, $ObjectIndex))
        {
            $ObjectIndex = BuildStructure($ObjectIndex, $CurrentFullName, $Array0Size, $Array1Size);
        }
        else
        {
            $ObjectIndex = BuildSingleItem($ObjectIndex, $CurrentFullName, $Array0Size, $Array1Size);
        }
    }

    push @headerlines, "\n} // ${PrependStr}fwvar namespace\n\n";
    push @headerlines, "#endif // $OutputMapNameGuard\n";

    open OUT, ">$OutputFolder/${OutputMapName}.h";
    print OUT @headerlines;
    close OUT;
}
    
sub BuildMapSource()
{
    my @sourcelines;
    push @sourcelines, "// Build Rev $MajorRev.$MinorRev\n\n";
    push @sourcelines, "// This definition will cause the .h below to actually instance the objects.\n";
    push @sourcelines, "#define TSP_MAP_DEFINE_VARIABLES\n";
    push @sourcelines, "#include \"${OutputMapName}.h\"\n";
    push @sourcelines, "#undef TSP_MAP_DEFINE_VARIABLES\n\n";
    push @sourcelines, "void ${PrependStr}variables()\n";
    push @sourcelines, "{\n";
    push @sourcelines, "using namespace ${PrependStr}fwvar;\n";
    push @sourcelines, "firmware_build = (char *)\"$MajorRev.$MinorRev\";\n\n";

    # Sort the names by address or by alpha if they're the same.
    my @sorted = sort { 
                      $ObjByIndex{$a}{address} <=> $ObjByIndex{$b}{address} 
                       or
                      $ObjByIndex{$a}{fwname} cmp $ObjByIndex{$b}{fwname}
                      } keys %ObjByIndex;
    # Just to align the fields in the source output, get the length of the longest variable.
    my $MaxLength = 0;
    foreach my $index (@sorted)
    {
        if ($ObjByIndex{$index}{include} > 0)   # include is set in the header section if we don't want to write a subcomponent address.
        {
            if (length $ObjByIndex{$index}{fwname} > $MaxLength)
            {
                $MaxLength = length $ObjByIndex{$index}{fwname};
            }
        }
    }
    # Now build the contents of the source file.
    foreach my $index (@sorted)
    {
        if ($ObjByIndex{$index}{include} > 0)   # include is set in the header section if we don't want to write a subcomponent address.
        {
            my $PadLength =  $MaxLength - length $ObjByIndex{$index}{fwname};
            push @sourcelines, $ObjByIndex{$index}{fwname} . ' ' x $PadLength . ' = 0x' . sprintf( "%08X", $ObjByIndex{$index}{address} ) . ";\n";
        }
    }
    push @sourcelines, "}\n";
    
    open OUT, ">$OutputFolder/${OutputMapName}.cpp";
    print OUT @sourcelines;
    close OUT;
}

sub BuildParameterFiles()
{
    open PARAM_FW_FILE, "<$ParameterFile" or die $!;
    open MISSING_FW_VAR_FILE, ">$OutputFolder/MissingFWVar.csv" or die $!;
    open MISSING_PARAM_FILE, ">$OutputFolder/MissingParam.csv" or die $!;
    open CPPOUT, ">$OutputFolder/VTEDevCommParameters.cpp" or die $!;
    open HOUT,   ">$OutputFolder/VTEDevCommParameters.h" or die $!;

    print HOUT "#ifndef VTEDEVCOMMPARAMETERS_H_\n";
    print HOUT "#define VTEDEVCOMMPARAMETERS_H_\n\n";
    print HOUT "struct fwParameter\n";
    print HOUT "{\n";
    print HOUT "    char * Parameter;\n";
    print HOUT "    void * Address; // FirmwareVariableName\n";
    print HOUT "    long Offset; // Byte offset from the variable\n";
    print HOUT "    long BaseSize; // byte integrals\n";
    print HOUT "    long BitSize;\n";
    print HOUT "    long BitOffset;\n";
    print HOUT "};\n";
    print HOUT "extern fwParameter fwParam[];\n\n";
    print HOUT "extern const unsigned int DevCommTotalParameters;\n";
    print HOUT "extern const char * const DevCommMemoryMapVersion;\n\n";
    print HOUT "#endif // VTEDEVCOMMPARAMETERS_H_\n";

    print MISSING_FW_VAR_FILE "The following FW variables were not found in the current build:\n";
    print MISSING_PARAM_FILE "The following variables do not have a parameter:\n";

    print CPPOUT "#include \"VTEDevCommParameters.h\"\n";
    print CPPOUT "#include \"Galaxy_m.h\"\n\n";
    print CPPOUT "using namespace fwvar;\n\n";
    print CPPOUT "fwParameter fwParam[] =\n{\n";

    my $line;
    my $FWName;
    my $ParamCount = 0;
    while ($line = <PARAM_FW_FILE>)
    {
        # Check for comment.
        if ($line =~ /^\/\//)
        {
            next;
        }
        $line =~ s/[\s]*$//g;        # can't just chomp due to DOS/Linux issues.
        $line =~ s/^[\s]*//g;        # can't just chomp due to DOS/Linux issues.
        my @Components = split(',', $line);

        $FWName = $Components[1];

        # Make sure there is a FW variable.
        if (length $FWName < 1)
        {
            print MISSING_FW_VAR_FILE "$Components[0],badly formed line\n";
            next;
        }

        # Check for an address offset.
        my $AddressOffset = 0;
        if ($FWName =~ /([\w\.]+)\+(\d+)/)
        {
            $FWName = $1;
            $AddressOffset = $2;
        }

        # TBD arrays need offsetting?
        my $FWNameFull = $FWName;
        $FWName =~ s/[\[\d\]]+\]$//;# Remove trailing arrays since they're not in the map.

        if (exists $ObjByName{$FWName})
        {
            print CPPOUT "    {\n";
            print CPPOUT "        (char *)\"$Components[0]\", // Parameter\n";
            print CPPOUT "        \&$FWNameFull, // FW variable\n";
            print CPPOUT "        $AddressOffset, // address offset\n";
            print CPPOUT "        $ObjByName{$FWName}{bytesize}, // byte size\n";
            print CPPOUT "        $ObjByName{$FWName}{bitsize}, // bit size\n";
            print CPPOUT "        $ObjByName{$FWName}{bitoffset}, // bit offset\n";
            print CPPOUT "    },\n";
            $ParamCount++;
        }
        else
        {
            print MISSING_FW_VAR_FILE "$Components[0],$FWName\n";
        }
    }
    print CPPOUT "};\n\n";
    print CPPOUT "const unsigned int DevCommTotalParameters = $ParamCount;\n";
    print CPPOUT "const char * const DevCommMemoryMapVersion = \"firmware version $MajorRev.$MinorRev\";\n\n";
    close CPPOUT;
    close PARAM_FW_FILE;
    close HOUT;

    foreach my $FWObj (keys %ObjByName)
    {
        print MISSING_PARAM_FILE "$FWObj\n";
    }

    close MISSING_FW_VAR_FILE;
    close MISSING_PARAM_FILE;
}

sub AddToObjectHash($$$$$$)
{
    my $fwname    = shift;
    my $address   = shift;
    my $bytesize  = shift;
    my $bitsize   = shift;
    my $bitoffset = shift;
    my $type      = shift;

    $ObjByName{$fwname}{address}       = $address;
    $ObjByName{$fwname}{bytesize}      = $bytesize;
    $ObjByName{$fwname}{bitsize}       = $bitsize;
    $ObjByName{$fwname}{bitoffset}     = $bitoffset;
    $ObjByName{$fwname}{type}          = $type;
    $ObjByIndex{$totalObjects}{address}   = $address;
    $ObjByIndex{$totalObjects}{bytesize}  = $bytesize;
    $ObjByIndex{$totalObjects}{bitsize}   = $bitsize;
    $ObjByIndex{$totalObjects}{bitoffset} = $bitoffset;
    $ObjByIndex{$totalObjects}{fwname}    = $fwname;
    $ObjByIndex{$totalObjects}{type}      = $type;
    $ObjByIndex{$totalObjects}{include}   = 1;
    $totalObjects++;
}

my $parentsize;
sub CreateObjectHash()
{
    # The regex is used for function objects.
    #      #  Symbol Name                Value      Bind  Sec  Type  Vis  Size
    #    415  __main                     0x0100044d   Gb    2  Code  Hi   0x8
    my $RegexCode = '^\s+\d+\s+(\S+)\s+0x([\da-f]{8})\s+Gb.+Code\s+\S+\s+0x([\da-f]+)';

    # The regex is used for data objects.
    my $RegexData = '^0x([\da-f]{8})\s+0x([\da-f]+)\(*(\d*):*(\d*)\)*[\s*]+(\S+)\s+(.+)';

    open FILE, "<$ObjectFile" or die $!;
    my @lines = <FILE>;
    close FILE;
    
    chomp(@lines);
    
    # Load the object hash (including non-array struct fields)
    # Since we may append a line, loop to 1 short of the end.
    for my $index (0..@lines-1)
    {
        my $line = $lines[$index];
        
    	# Arm decided to occasionally throw in a line feed in their symbol table if they decided a line was too long.
    	# Check for this signature and append the next line if it's a match.
    	if ($line =~ /^\s+\d+\s+\S+$/)
    	{
    		$line .= $lines[$index + 1];
    	}
        # Collect any function addresses.
        if ( $line =~ /$RegexCode/ )
        {
            my $fwname = $1;
            my $address = hex $2;
            my $bytesize = hex $3;
            my $bitsize = 0;
            my $bitoffset = 0;
            my $type = 'Code';
            AddToObjectHash($fwname, $address, $bytesize, $bitsize, $bitoffset, $type);
        }

        # Collect any data object addresses.
        if ( $line =~ /$RegexData/ )
        {
            my $address   = hex $1 ;
            my $bytesize  = hex $2 ;
            my $fwname    = $5;
            my $type      = $6;
            # Bit size and offset may not exist. If not, set the bitsize using the byte size and 0 the offset.
            my $bitoffset;
            my $bitsize;
            # Indicates we have a bit field.
            if (length $3 > 0)
            {
                # Due to an oddity in the fromelf output, the byte offset needs adjustment.
                # For some reason the bytesize of a field reflects the type rather than the actual size (after packing)
                # yet the size of the struct parent is correct. Since we're in a field, use the parent size.
                $bytesize = $parentsize;

                $bitoffset = $3;
                $bitsize   = $4;
                # Due to an oddity in the fromelf output, the bit offset needs adjustment.
                # fromelf calculates the bitoffset FROM THE LEFT (though they call the right-most bit 0)
                # So, adjust the value so it's what we're expecting.
                $bitoffset = (8 * $bytesize) - $bitoffset - $bitsize;
            }
            else
            {
                # This could be a struct parent so save it.
                $parentsize = $bytesize;
                $bitoffset = 0;
                $bitsize   = $bytesize * 8;
            }
            AddToObjectHash($fwname, $address, $bytesize, $bitsize, $bitoffset, $type);
        }
    }
}

CreateObjectHash();
BuildMapHeader();
BuildMapSource();
BuildParameterFiles();

my $PerlName = ( split( /\//, $0 ) )[-1];
print "$PerlName Processed $totalObjects objects\n";
